<?php
namespace app\command;


use think\console\Command;
use think\console\Input;
use think\console\Output;

class Log extends Command
{

    /**
     * 设置的日志类型
     */
    const LOG_TYPE_LIST = [
        'TRADE_LOG' => 'trade_log', // 下单请求日志
        'TRADE_ERROR' => 'error_trade', // 下单请求失败
        'ERROR' => 'error',
        'TAKE_TIME' => 'takeTime',
        'UDP_API'   => 'udpApi',
        'MONITORY_ACCOUNT'  => 'monitoryAccount',   //账户监控情况
        'UDP_API_ERR'   => 'error_udpApi',  // udp 请求错误
        'DEVIATIONS_CORRECT'   => 'deviations_correct',  // udp 中修正系统偏差量
        'MONITOR_PROCESS'   => 'monitorProcess', // 监控进程数据
        'PROCESS_ERROR'   => 'error_process', // 进程执行出现报错
        'PROCESS_WAIT_ERROR'   => 'error_processWait', //等待进程报错
        'PROCESS_ADN_RESTART_START'   => 'processAndRestartStart',
        'PROCESS_ADN_RESTART_ERROR'   => 'error_processAndRestart',
        'SQL_ERROR'   => 'error_sql',   // 数据执行错误记录
        'DEDUCT_ERROR'   => 'error_deduct',   // 折扣错误
        'HTTP_RECORD'   => 'httpRecord',   // HTTP 记录 HTTP请求数据
        'HTTP_ERROR'   => 'error_http',   // HTTP 请求发生错误
        'CPU'   => 'cpu',   // 统计cpu使用率
        'COUNT'   => 'count',
    ];

    /**
     * 设置日志保存大小 【单位 M】
     */
    const TYPE_SAVE_SIZE = [
        'ONE_MAX'   => 20,      //单个文件最大大小
        'DEFAULT'   => 512,     //默认
        'error'     => 512,     //错误日志
        'blend'     => 512,     //混合保存
        'takeTime'  => 10240,    //执行日志记录
        'udpApi'  => 512,       //请求同步API的日志
        'httpRecord'  => 512,       //记录 http记录信息
        'monitoryAccount'   => 512, //保存账户监控情况
        'deviations_correct'   => 100, //保存修改余量偏差量
        'error_udpApi'  => 512,       //请求同步API的日志
        'error_trade'  => 100,       //下单请求失败
        'processAndRestartStart'     => 100, //记录循环含重启的进程启动记录
        'error_process'     => 100, // 进程报错
        'error_processWait'     => 100, // 等待进程报错
        'error_processAndRestart'     => 100, //记录循环含重启的进程报错记录
        'error_sql'     => 1024, // sql 错误记录
        'error_deduct'  => 40,// 抵扣错误日志
        'error_http'    => 40,// HTTP 服务器出现的错误
        'cpu'       => 40,// CPU 使用日志
        'trade_log'       => 100,// 下单请求日志
        'count'     => 512, // 负责统计用
    ];

    const SLEEP_TIME = 5; // 整理日志时间，每分钟一次

    // 保存日志的目录
    private static $logDir = [
        'root'  => null,
    ];


    /**
     * 重写configure
     * {@inheritdoc}
     */
    protected function configure()
    {
        // 命令的名字（"think" 后面的部分）
        $this->setName('Log')
            // 运行 "php think list" 时的简短描述
            ->setDescription('日志写入处理')
            // 运行命令时使用 "--help" 选项时的完整命令描述
            ->setHelp("一直执行 无参数");
    }

    /**
     *  * 重写execute
     *  * {@inheritdoc}
     *
     * @param Input $input
     * @param Output $output
     */
    public function execute(Input $input, Output $output)
    {

        // 开启日志写入
        $this->startSave();

        //开启等待
        Swoole::wait();
    }

    /**
     * 启用后台进程处理
     */
    private function startSave() {
        Swoole::startProcess(function (){
            $sizeList = static::TYPE_SAVE_SIZE;
            foreach ($sizeList as &$value) {
                $value *= 1024 * 1024;
            }
            while (true) {
                try {
                    $this->saveLog($sizeList);
                }catch (\Exception $e) {
                }
                sleep(self::SLEEP_TIME);
            }
        },true);
    }


    /**
     * 执行日志保存处理
     */
    private function saveLog($sizeList) {
        $saveName = 'save';
        $filename = self::getFilename(time() - 2);
        $rootPath = self::getDir('');
        $logTypeList = scandir($rootPath);
        foreach($logTypeList as $type) {
            if(in_array($type,['.','..']) || !is_dir($rootPath . $type)) {
                continue;
            }
            $maxSize = array_key_exists($type,$sizeList)?$sizeList[$type]:$sizeList['DEFAULT'];
            $typeDir = $rootPath . $type . '/';
            $fileList = scandir($typeDir);
            sort($fileList);

            // 获取保存目录信息
            $saveDir = self::getDir($type . '/' . $saveName);
            $saveFileList = scandir($saveDir);
            sort($saveFileList);
            $lastSaveFile = $saveDir . $saveFileList[count($saveFileList) - 1];
            $saveCountSize = 0;
            foreach($fileList as $logFilename) {
                $logfile = $typeDir . $logFilename;
                if(!is_file($logfile) || $logFilename >= $filename) {
                    continue;
                }
                // 判断需要写入的文件
                if(!$saveCountSize) {
                    if(is_file($lastSaveFile)) {
                        clearstatcache();
                        $saveCountSize = filesize($lastSaveFile);
                    } else {
                        $lastSaveFile = $saveDir . $logFilename;
                        $saveFileList[] = $logFilename;
                    }
                }

                // 判断单文件是否已超出设置大小
                if($saveCountSize > $sizeList['ONE_MAX']) {
                    $saveCountSize = 0;
                    $lastSaveFile = $saveDir . $logFilename;
                    $saveFileList[] = $logFilename;
                }
                //搬移日志
                file_put_contents($lastSaveFile,file_get_contents($logfile),FILE_APPEND);
                // 删除日志
                @unlink($logfile);


                /************** 保存最大数量，删除久记录 ****************/
                rsort($saveFileList);
                $saveSizeCount = 0;
                foreach($saveFileList as $file) {
                    $saveFilename = $saveDir . $file;
                    if(!is_file($saveFilename)) {
                        continue;
                    }
                    if($saveSizeCount > $maxSize) {
                        @unlink($saveFilename);
                        continue;
                    }
                    clearstatcache();
                    $saveSizeCount += filesize($saveFilename);
                }
                /************ END 保存最大数量，删除久记录 **************/
            }
        }
    }

    /**
     * 写入日志
     * @param $message  日志信息
     * @param null $type    日志类型
     * @param bool $encode   是否自动编码
     * @return bool
     */
    static function write($message,$type = null,$encode = true) {

        $type = empty($type)?'blend':$type;
        try{
            $filename = self::getDir($type) . self::getFilename();
            $message = $encode?self::encode($message):$message;
            file_put_contents($filename,time() . ' ' . $message . "\n",FILE_APPEND);
        }catch (\Exception $e) {
            return false;
        }
        return true;
    }


    /**
     * 将日志信息编译成字符串
     * @param $params
     */
    static function encode($params) {
        $result =  base64_encode(serialize($params));
        return $result;
    }

    /**
     * 反编码成内容
     * @param string $data
     */
    static function decode(string $data) {
        return unserialize(base64_decode($data));
    }


    /**
     * 获取目录地址
     * @param $type
     * @return string
     */
   static private function getDir($type) {
        if(is_null(self::$logDir['root'])) {
            self::$logDir['root'] = dirname(__FILE__) . '/log/';
        }
        $dir = self::$logDir['root'] . (empty($type)?'':($type . '/'));
        is_dir($dir) || mkdir($dir,0777,true);
        return $dir;
    }

    /**
     * 获取文件名
     * @param $time 时间
     */
    static private function getFilename($time = null) {
        $time = empty($time)?time():$time;
        return  $time . '_' . date('Ymd_His',$time) . '.log';;
    }

    /**
     * 添加cpu统计日志
     * @param string $tag
     * @param bool $isStart
     */
    static public function cpu(string $tag,$isStart = false) {
        static $tagList = [];
        if($isStart && array_key_exists($tag,$tagList)) {
            unset($tagList[$tag]);  // 删除记录
        }
        $time = microtime(true);
        $rup = getrusage();
        $details = [
            'time'  => $time,
            'userTime'  => bcadd($rup['ru_utime.tv_sec'],bcdiv($rup['ru_utime.tv_usec'],1000000,10),10) -0,
            'sysTime'   => bcadd($rup['ru_stime.tv_sec'],bcdiv($rup['ru_stime.tv_usec'],1000000,10),10) -0,
        ];
        if(!array_key_exists($tag,$tagList)) {
            $tagList[$tag] = $details;
            return true;
        }
        $startDetails = $tagList[$tag];
        unset($tagList[$tag]);
        foreach($details as $key=>$value) {
            $details[$key] = bcsub($value,$startDetails[$key],10);
        }
        $result = [[
            'user'  => bcmul(bcdiv($details['userTime'],$details['time'],10),100,2) - 0,
            'sys'  => bcmul(bcdiv($details['sysTime'],$details['time'],10),100,2) - 0,
            'count'  => bcmul(bcdiv(bcadd($details['userTime'],$details['sysTime'],10),$details['time'],10),100,2) - 0,
        ],$details];
        // 写入日志
        static::write(implode(' ',[$tag,$result[0]['count'],serialize($result)]),self::LOG_TYPE_LIST['CPU'],false);
        return $result;
    }


}
